/*
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package simple.compiler;

import simple.classfiles.ClassFile;
import simple.compiler.parser.Parser;
import simple.compiler.scanner.Scanner;
import simple.compiler.symbols.NamespaceSymbol;
import simple.compiler.symbols.ObjectSymbol;
import simple.compiler.types.ObjectType;
import simple.compiler.util.Signatures;
import simple.util.Execution;
import simple.util.Files;

import com.android.apkbuilder.ApkBuilder;
import com.android.dx.command.dexer.DxContext;
import com.android.dx.command.dexer.Main.Arguments;
import com.android.dx.dex.code.PositionList;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.imageio.ImageIO;

/**
 * Simple编译器的主要入口点。
 * 
 * <p>为构建和部署Simple项目提供入口点。
 *
 * @author Herbert Czymontek
 * @author 东子 xhwsd@qq.com
 */
public final class Compiler {

  // 可执行文件扩展名
  private static final String EXECUTABLE_EXTENSION =
      System.getProperty("os.name").startsWith("Windows") ? ".exe" : "";

  // 文件名和位置
  private static final String JAVA_HOME = System.getenv("JAVA_HOME");
  private static final String ANDROID_HOME = System.getenv("ANDROID_HOME");
  private static final String SIMPLE_HOME = System.getenv("SIMPLE_HOME");
  private static final String KEY_PASSWORD = System.getenv("KEY_PASSWORD");

  // 二进制文件路径
  private static final String JARSIGNER_BINARY =
      JAVA_HOME + "/bin/jarsigner" + EXECUTABLE_EXTENSION;
  private static final String KEYTOOL_BINARY =
      JAVA_HOME + "/bin/keytool" + EXECUTABLE_EXTENSION;
  private static final String AAPT_BINARY =
      ANDROID_HOME + "/platforms/android-28/tools/aapt" + EXECUTABLE_EXTENSION;

  // 运行时
  private static final String ANDROID_RUNTIME = ANDROID_HOME + "/platforms/android-28/android.jar";
  private static final String SIMPLE_ANDROID_RUNTIME = SIMPLE_HOME + "/SimpleAndroidRuntime.jar";

  // 签名APK文件的密钥库
  private static final String ANDROID_DEBUG_KEYSTORE = "android_debug.keystore";
  
  /**
   * 目标编译平台。
   */
  public enum Platform {

    None(""),
    Android(SIMPLE_ANDROID_RUNTIME);

    // Simple运行时库
    private final String runtimeLibrary;

    Platform(String runtimeLibrary) {
      this.runtimeLibrary = runtimeLibrary;
    }

    public String getRuntimeLibrary() {
      return runtimeLibrary;
    }
  }

  /**
   * Simple运行时的根包名称。所有运行时库包都是此包的子包。
   */
  public static final String RUNTIME_ROOT_PACKAGE = "simple.runtime";
  public static final String RUNTIME_ROOT_INTERNAL = RUNTIME_ROOT_PACKAGE.replace('.', '/');

  /**
   * 运行时错误超类的内部名称
   */
  public static final String RUNTIME_ERROR_INTERNAL_NAME =
      RUNTIME_ROOT_INTERNAL + "/errors/运行错误";

  // 日志记录支持
  private static final Logger LOG = Logger.getLogger(Compiler.class.getName());

  // 编译器要使用的标准和错误流
  private PrintStream out;
  private PrintStream err;

  // 源文件的字符编码
  private String encoding;

  // 目标平台
  private Platform platform;

  // 正在编译的对象列表
  private List<ObjectSymbol> objects;

  // 生成的类文件列表
  private List<String> classfiles;

  // 编译错误和警告数
  private int errorCount;
  private int warningCount;

  // 全局（未命名）命名空间符号
  private NamespaceSymbol globalNamespaceSymbol;

  // 运行时错误类型
  private final ObjectType runtimeErrorType;

  // 文件索引到文件名映射
  private final List<String> filenameMap;

  // Map from object symbols of components needing Android permissions to the corresponding class
  // Note that this included components that may not be referenced by the currently compiling
  // package
  private final Map<ObjectSymbol, Class<?>> symbolToClassNeedingPermissionsMap;

  // Set of component classes referenced by the current package that require Android permissions
  private Set<Class<?>> classesNeedingPermissions;

  // 运行时库加载器和分析器
  private RuntimeLoader runtimeLoader;

	// Simple项目使用服务集合
	private Set<String> simpleNeedingServices;

  /**
   * Add a component class to the list of Simple component classes that require Android permissions.
   *
   * @param usesPermissionsClass the component class specifying the required permission.
   * @param usesPermissionObjectSymbol the {@link ObjectSymbol} corresponding to usesPermissionClass
   */
  public void addToPermissions(Class<?> usesPermissionsClass,
                               ObjectSymbol usesPermissionObjectSymbol) {
    symbolToClassNeedingPermissionsMap.put(usesPermissionObjectSymbol, usesPermissionsClass);
  }

  /**
   * Check to see if this object symbol corresponds to a Simple component class that requires
   * Android permissions and add it to the set of such classes.
   *
   * @param objectSymbol the object symbol to check
   */
  public void checkForPermissions(ObjectSymbol objectSymbol) {
    if (symbolToClassNeedingPermissionsMap.containsKey(objectSymbol)) {
      classesNeedingPermissions.add(symbolToClassNeedingPermissionsMap.get(objectSymbol));
    }
  }

  /*
   * Generate the set of Android permissions needed by this package.
   *
   * Note that we do a bunch of reflection here rather than directly using the
   * {@link UsesPermissions} class and its methods. This is because the {@link UsesPermissions}
   * class that would be directly referenced here is actually a different class object than the one
   * created for the component annotations. This is due to them being from different class
   * loaders. Consequently, attempts to do things like 'getAnnotation(UsesPermissions.class)' or
   * to cast the annotation objects to UsesPermissions all failed.
   */
  private Set<String> generatePermissions() {
    Set<String> permissions = new HashSet<String>();
    final Class<? extends Annotation> usesPermissionAnnotationClass =
        runtimeLoader.getAndroidUsesPermission();
    java.lang.reflect.Method permissionNameMethod = null;
    try {
      permissionNameMethod = usesPermissionAnnotationClass.getMethod("permissionNames");
    } catch (NoSuchMethodException e) {
      LOG.log(Level.SEVERE,
              "Simple compiler doesn't know about UsesPermissions's permissionNames method.");
    }
    for (Class<?> classNeedingPermission : classesNeedingPermissions) {
      final Annotation usesPermissionsAnnotation =
          classNeedingPermission.getAnnotation(usesPermissionAnnotationClass);
      try {
        if (usesPermissionsAnnotation != null) {
          final String permissionsString =
              (String) permissionNameMethod.invoke(usesPermissionsAnnotation);
          if (permissionsString != null && permissionsString.length() > 0) {
            final String[] permissionStrings = permissionsString.split(",");
            for (String permissionString : permissionStrings) {
              permissions.add(permissionString.trim());
            }
          }
        } else {
          LOG.log(Level.SEVERE, "Class doesn't have UsesPermissions annotation: "
                                + classNeedingPermission.getName());
        }
      } catch (InvocationTargetException e) {
        LOG.log(Level.SEVERE,
                "Simple compiler doesn't know how to deal with UsesPermissions annotation.");
      } catch (IllegalAccessException e) {
        LOG.log(Level.SEVERE,
                "Simple compiler doesn't know how to deal with UsesPermissions annotation.");
      }
    }
    return permissions;
  }

  /*
   * Creates an AndroidManifest.xml file needed for deploying the built Android application.
   */
  private static boolean writeAndroidManifest(Compiler compiler, Project project,
      File buildDirectory, Set<String> permissionsNeeded) {
    // Create AndroidManifest.xml
    String mainForm = project.getMainForm();
    File manifest = new File(buildDirectory, "AndroidManifest.xml");
    try {
      OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(manifest), "UTF-8");
      out.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");

      // 清单头
      out.write("<manifest " +
          "xmlns:android=\"http://schemas.android.com/apk/res/android\" " +
          // 应用包名
          "package=\"" + Signatures.getPackageName(mainForm) + "\" " +
          // 版本号，注意必须为整数
          "android:versionCode=\"" + project.getVersionCode() + "\" " +
          // 版本名
          "android:versionName=\"" + project.getVersionName() + "\">\n");

      // 应用对不同版本平台兼容性
      out.write("    <uses-sdk " +
          // 指定应用运行所需最低API级别
          // API14对应版本为Android 4.0
          "android:minSdkVersion=\"14\" " +
          // 指定应用的目标API级别，该属性添加于API4
          // 根据《移动应用软件高API等级预置与分发自律公约》自2019年5月1日起，
          // 新上架应用应基于Android 8.0（API等级26）及以上开发。
          // API28对应版本为Android 9.0
          "android:targetSdkVersion=\"28\"/>\n");

      // 应用对屏幕大小支持机制，该节点添加于API4
      out.write("    <supports-screens " +
          // 是否支持小屏
          "android:smallScreens=\"true\" " +
          // 是否支持中屏
          "android:normalScreens=\"true\" " +
          // 是否支持大屏
          "android:largeScreens=\"true\" " +
          // 是否支持超大屏，该属性添加于API9
          "android:xlargeScreens=\"true\" " +
          // 是否支持多种不同密度
          "android:anyDensity=\"true\"/>\n");

      // 使用权限
      out.write("    <!-- Uses Permission Start -->\n");
      for (String permission : permissionsNeeded) {
        out.write("    <uses-permission android:name=\"" + permission + "\" />\n");
      }
      out.write("    <!-- Uses Permission End -->\n");

      // 应用
      out.write("    <application " +
          // 应用包全名
          "android:name=\"" + Compiler.RUNTIME_ROOT_PACKAGE + ".android.MainApplication\" " +
          // 应用图标
          "android:icon=\"" + project.getIcon() + "\" " +
          // 应用名
          "android:label=\"" + project.getProjectName() + "\" " +
          // 硬件加速，该属性添加于API11
          "android:hardwareAccelerated=\"true\">\n");

      // 活动
      out.write("        <activity " +
          // 活动包全名
          "android:name=\"simple.runtime.android.MainActivity\" " +
          // 活动图标，引用res中的资源图标
          "android:icon=\"" + project.getIcon() + "\" " +
          // 活动名称
          "android:label=\"" + project.getProjectName() + "\" " +
          // 活动方向
          "android:screenOrientation=\"" + project.getOrientation() + "\" " +
          // 活动主题，缺省不设置该值使用系统默认主题
          (project.getTheme().isEmpty() ? "" : "android:theme=\"" + project.getTheme() + "\" ") +
          // 配置改变重启应用过滤器（如果不定义以下值，应用可能重启）：
          // keyboard - 键盘模式发生变化，例如：用户接入外部键盘输入。
          // keyboardHidden - 用户打开手机硬件键盘
          // navigation - 导航类型
          // orientation - 屏幕方向
          // screenSize - 屏幕尺寸，该属性添加于API13
          // smallestScreenSize - 物理屏幕尺寸，该属性值添加于API13
          "android:configChanges=\"keyboard|keyboardHidden|navigation|orientation|screenSize|smallestScreenSize\" " +
          // 活动与软键盘的交互模式，adjustPan：不改变窗口尺寸，而是自动平移窗口内容
          "android:windowSoftInputMode=\"adjustPan\" " +
          // 启动模式，singleTask：仅创建一个实例，且允许与其它活动组成部分
          "android:launchMode=\"singleTask\">\n");

      // 元数据 应用主窗口限制类名
      out.write("            <meta-data " +
          // 固定键名(运行库根包名.android.MainForm)
      	  "android:name=\"simple.runtime.android.MainForm\" " +
          // 主活动（运行库根包名.android.MainForm）启动后会读取该值，并设置该值（类名）为主窗口
      	  "android:value=\"" + mainForm + "\" />\n");
      
      // 意图过滤器，指定该应用将接受那些意图广播
      out.write("            <intent-filter>\n");
      out.write("                <action android:name=" +
          "\"android.intent.action.MAIN\" />\n");
      out.write("                <category android:name=" +
          "\"android.intent.category.LAUNCHER\" />\n");
      out.write("                <category android:name=" +
          "\"android.intent.category.DEFAULT\" />\n");
      out.write("            </intent-filter>\n");

      out.write("        </activity>\n");

      // 服务
      out.write("        <!-- Service Start -->\n");
      for (String serviceName : compiler.simpleNeedingServices) {
        out.write("        <service android:name=\"" + serviceName + "\">\n");
        out.write("            <intent-filter>\n");
        out.write("                <action android:name=\"" + serviceName + "\" />\n");
        out.write("            </intent-filter>\n");
        out.write("        </service>\n");
      }
      out.append("        <!-- Service End -->\n");

      // max_aspect 属性表示 App 能够支持的最大屏幕比例，官方建议我们将该值设置为 2.1 或者更高的值。
      out.write("        <meta-data " +
          "android:name=\"android.max_aspect\" " +
          "android:value=\"2.1\" />\n");

      
      out.write("    </application>\n");
      out.write("</manifest>\n");
      out.close();
      return true;
    } catch (IOException e) {
      compiler.error(Scanner.NO_POSITION, Error.errWriteError, manifest.toString());
      return false;
    }
  }

  /**
   * 内部错误，调用前应指示意外情况。
   */
  public static void internalError() {
    throw new IllegalStateException("内部错误");
  }

  /**
   * 构建一个Simple的项目。
   *
   * @param platform  要构建的目标平台
   * @param project  要生成的项目
   * @param out  要重定向到的标准输出流
   * @param err  要重定向到的标准错误流
   * @return  如果编译成功为{@code true}，否则为{@code false}
   */
  public static boolean compile(Platform platform, Project project, PrintStream out,
      PrintStream err) {
    // 为编译创建新的编译器实例
    long start = System.currentTimeMillis();
    final Compiler compiler = new Compiler(platform, out, err);

    // 解析所有源文件
    for (Project.SourceDescriptor arg : project.getSources()) {
      String fileName = arg.getQualifiedName().replace('.', '/') + Project.SOURCEFILE_EXTENSION;
      out.println("正在编译单元：" + fileName);

      try {
        new Parser(compiler, new Scanner(compiler, compiler.createFileIndex(fileName),
            Files.read(arg.getFile(), compiler.encoding)), arg.getQualifiedName()).parse();
      } catch (IOException ioe) {
        compiler.error(Scanner.NO_POSITION, Error.errReadError, arg.getFile().toString());
      }
    }

    // 解析树中的符号信息
    compiler.resolve();

    // 如果没有编译错误，则创建并打包类文件
    if (compiler.errorCount > 0) {
      compiler.out.println("编译发生错误计数：" + compiler.errorCount);
    } else {
      switch (compiler.platform) {
      default:
        Compiler.internalError();
        return false; // 永远不会到这里...

      case Android:
        // 检验图标属性格式有效，格式：@drawable/图标名
        String iconPath = project.getIcon();
        Matcher iconMatcher = Pattern.compile("@drawable/(\\S+)").matcher(iconPath);
        if (!iconMatcher.find()) {
          compiler.error(Scanner.NO_POSITION, "属性配置项的应用图标 %1 无效", iconPath);
          return false;
        }

        // 写应用缺省图标
        File resDir = Files.createDirectory(project.getResDirectory());
        File drawableDir = Files.createDirectory(resDir, "drawable");
        File iconFile = new File(drawableDir, iconMatcher.group(1) + ".png");
        if (!iconFile.exists()) {
          compiler.out.println("正在生成缺省图标：" + iconFile.getName());
          URL iconURL = Compiler.class.getResource("/simple/compiler/resources/simple.png");
          assert iconURL != null;
          try {
            BufferedImage icon = ImageIO.read(iconURL);
            ImageIO.write(icon, "png", iconFile);
          } catch (IOException ioe) {
            compiler.error(Scanner.NO_POSITION, Error.errWriteError, iconFile.toString());
            return false;
          }
        }

        // Create and pre-process AndroidManifest.xml
        out.println("正在生成清单：AndroidManifest.xml");
        File buildDir = Files.createDirectory(project.getBuildDirectory());
        if (!writeAndroidManifest(compiler, project, buildDir, compiler.generatePermissions())) {
          return false;
        }

        // 创建类文件
        File classesDir = Files.createDirectory(buildDir, "classes");
        compiler.generate(classesDir);

        // 调用 dx.jar 对类文件生成 classes.dex
        File dexDir = Files.createDirectory(buildDir, "dex");
        File dexFile = new File(dexDir, "classes.dex");
        out.println("正在生成可执行类：" + dexFile.getName());
        try {
          // 故意使用 System.err 和 System.out。不想用工具输出污染构建消息
          DxContext context = new DxContext(System.out, System.err);
          // Arguments注释信息：
          // https://android.googlesource.com/platform/dalvik/+/a9ac3a9d1f8de71bcdc39d1f4827c04a952a0c29/dx/src/com/android/dx/command/dexer/Main.java
          Arguments args = new Arguments();
          // 二进制文件的输出文件名
          args.outName = dexFile.getAbsolutePath();
          // 二进制输出是否是 .jar文件，而不是普通的 .dex
          args.jarOutput = false;
          // 是否保留局部变量信息
          args.localInfo = true;
          // 要保留多少源位置信息
          args.positionInfo = PositionList.LINES;
          // 文件名参数
          args.fileNames = new String[] {
              classesDir.getAbsolutePath(),
              SIMPLE_ANDROID_RUNTIME
          };
          // ...\android\platforms\android-等级\tools\lib\dx.jar
          if (new com.android.dx.command.dexer.Main(context).runDx(args) != 0) {
            internalError();
            return false;
          }
        } catch (Throwable t) {
          internalError();
          return false;
        }

        // 调用 aapt.exe 打包资源生成 .ap_
        Files.createDirectory(project.getAssetsDirectory());
        File deployDir = Files.createDirectory(buildDir, "deploy");
        File apFile = new File(deployDir, project.getProjectName() + ".ap_");
        out.println("正在打包资源：" + apFile.getName());
        String aaptPackageCommandline[] = {
            AAPT_BINARY,
            // 打包
            "package",
            // 输出详细信息
            //"-v",
            // 如果编译出来的文件已经存在，强制覆盖。
            "-f",
            // AndroidManifest.xml的路径
            "-M", buildDir.getAbsolutePath() + File.separator + "AndroidManifest.xml",
            // 收集res资源，可多个
            "-S", resDir.getAbsolutePath(),
            // 收集assets资源，可多个
            "-A", project.getAssetsDirectory().toString(),
            // 指定版本平台的android.jar的路径
            "-I", ANDROID_RUNTIME,
            // 具体指定apk文件的输出
            "-F", apFile.getAbsolutePath()
            // 自动添加重叠包里的资源，参考文档 https://www.jianshu.com/p/8c373d5b0464
            //"--auto-add-overlay"
        };
        // Using System.err and System.out on purpose. Don't want to pollute build messages with
        // tools output
        if (!Execution.execute(null, aaptPackageCommandline, System.out, System.err)) {
          internalError();
          return false;
        }

        // 调用 apkbuilder.jar 将资源和类文件整合成生成 .apk
        File apkFile = new File(deployDir, project.getProjectName() + ".apk");
        compiler.out.println("正在生成应用：" + apkFile.getName());
        String apkBuilderCommandline[] = {
            // 欲生成apk的文件路径
            apkFile.getAbsolutePath(),
            // 输出详细信息
            //"-v",
            // 创建一个无签名的包
            "-u",
            // 指定apk资源路径
            "-z", apFile.getAbsolutePath(),
            // 指定dex文件路径
            "-f", dexFile.getAbsolutePath()
            // 引用的第三方jar，可多个
            // "-rj 路径"
            // 引用的第三方so，可多个
            // "-nf 路径"
        };
        ApkBuilder.main(apkBuilderCommandline);

        // Sign the apk file: if the project file lists a keystore, build a release version of the
        // application. Otherwise generate a debug keystore, if necessary, and use it to sign the
        // application
        String jarsignerCommandline[];
        String keyLocation = project.getKeystoreLocation();
        if (keyLocation != null && !keyLocation.isEmpty() && KEY_PASSWORD != null && !KEY_PASSWORD.isEmpty()) {
          // 调用 jarsigner.exe 使用发布密钥签名应用。
          out.println("正在发布签名：" + apkFile.getName() + " -" + project.getKeystoreAlias());
          String releaseJarsignerCommandline[] = {
              JARSIGNER_BINARY,
              // [-keystore <url>] 密钥库位置
              "-keystore", keyLocation,
              // [-storepass <口令>] 用于密钥库完整性的口令
              "-storepass", KEY_PASSWORD,
              // JAR/APk包文件
              apkFile.getAbsolutePath(),
              // 别名
              project.getKeystoreAlias()
          };
          jarsignerCommandline = releaseJarsignerCommandline;
        } else {
          File keystore = new File(buildDir, ANDROID_DEBUG_KEYSTORE);
          if (!keystore.exists()) {
            // 调用 keytool.exe 生成调试密钥。
            out.println("正在生成调试密钥：" + ANDROID_DEBUG_KEYSTORE);
            String keytoolCommandline[] = {
                KEYTOOL_BINARY,
                // 生成密钥库
                "-genkey",
                // [-keystore <keystore>] 密钥库名称
                "-keystore", keystore.getAbsolutePath(),
                // [-alias <alias>] 要处理的条目的别名
                "-alias", "AndroidDebugKey",
                // [-keyalg <keyalg>] 密钥算法名称
                "-keyalg", "RSA",
                // [-storepass <arg>] 密钥库口令
                "-storepass", "android",
                // [-keypass <arg>] 密钥口令
                "-keypass", "android",
                // [-dname <dname>] 唯一判别名
                "-dname", "CN=Android Debug, O=Android, C=US",
                // [-validity <valDays>] 有效天数
                "-validity", "365"
            };
            // 不输出警告信息
            if (!Execution.execute(null, keytoolCommandline, null, System.err)) {
              internalError();
              return false;
            }
          }

          // 调用 jarsigner.exe 使用调试密钥签名应用。
          out.println("正在调试签名：" + apkFile.getName() + " -AndroidDebugKey");
          String debugJarsignerCommandline[] = {
              JARSIGNER_BINARY,
              // [-keystore <url>] 密钥库位置
              "-keystore", keystore.getAbsolutePath(),
              // [-storepass <口令>] 用于密钥库完整性的口令
              "-storepass", "android",
              // [-digestalg <算法>] 摘要算法的名称
              //"-digestalg", "SHA1",
              // [-sigalg <算法>] 签名算法的名称
              //"-sigalg", "SHA1withRSA",
              // JAR/APk包文件
              apkFile.getAbsolutePath(),
              // 别名
              "AndroidDebugKey"
          };
          jarsignerCommandline = debugJarsignerCommandline;
        }

        // 调用 jarsigner.exe 签名应用。不输出警告信息
        if (!Execution.execute(null, jarsignerCommandline, null, System.err)) {
          internalError();
          return false;
        }
        break;
      }
    }

    out.println("构建完成，用时 " +
        ((System.currentTimeMillis() - start) / 1000.0) + " 秒");

    return compiler.errorCount == 0;
  }

  /**
   * 为单元测试执行构建一个Simple项目。
   *
   * @param  project  要构建的项目
   * @return  如果有任何错误，则返回{@code null}
   */
  public static List<ClassFile> compileForUnitTesting(Project project) {
    // Create a new compiler instance for the compilation
    long start = System.currentTimeMillis();
    final Compiler compiler = new Compiler(Platform.Android, System.out, System.err);

    // Parse all source files
    for (Project.SourceDescriptor arg : project.getSources()) {
      final File file = arg.getFile();
      System.out.println("________编译 " + file.getPath());

      try {
        new Parser(compiler, new Scanner(compiler, compiler.createFileIndex(file.getAbsolutePath()),
            Files.read(file, compiler.encoding)), arg.getQualifiedName()).parse();
      } catch (IOException ioe) {
        compiler.error(Scanner.NO_POSITION, Error.errReadError, arg.getFile().toString());
      }
    }

    // Resolve symbol information in parse trees
    compiler.resolve();

    // Create and package class files if there were no compilation errors
    List<ClassFile> classes = null;
    if (compiler.errorCount > 0) {
      compiler.out.println("Compilation errors: " + compiler.errorCount);
    } else {
      // Create class files
      classes = new ArrayList<ClassFile>();
      compiler.generate(classes);
    }

    System.out.println("Build finished in " +
        ((System.currentTimeMillis() - start) / 1000.0) + " seconds");

    return classes;
  }

  /**
   * 创建一个新的Simple编译器。
   * 
   * @param platform  platform to compile for
   * @param out  stdout stream for compiler messages
   * @param err  stderr stream for compiler messages
   */
  public Compiler(Platform platform, PrintStream out, PrintStream err) {
    this.out = out;
    this.err = err;
    // xhwsd@qq.com 2021-5-22 指定源文件（.simple）字符编码
    encoding = "UTF-8";
    objects = new ArrayList<ObjectSymbol>();
    classfiles = new ArrayList<String>();
    globalNamespaceSymbol = new NamespaceSymbol();
    symbolToClassNeedingPermissionsMap = new HashMap<ObjectSymbol, Class<?>>();
    classesNeedingPermissions = new HashSet<Class<?>>();

		// Simple项目使用服务（包名）集合
		simpleNeedingServices = new HashSet<String>();

    // 加载Simple的运行时库符号信息
    this.platform = platform;
    if (platform != Platform.None) {
      // TODO: 支持第三方简单库
      runtimeLoader = new RuntimeLoader(this, ANDROID_RUNTIME, platform.getRuntimeLibrary());
      runtimeLoader.loadSimpleObjects();
    }

    filenameMap = new ArrayList<String>();

    runtimeErrorType = (ObjectType) ObjectSymbol.getObjectSymbol(this,
          RUNTIME_ERROR_INTERNAL_NAME).getType();
  }

  /**
   * Returns the path name of the file associated with the file index
   *
   * @param fileIndex  index of file to lookup
   * @return  file name of file being looked up
   */
  public String fileIndexToPath(int fileIndex) {
    return fileIndex == 0 ? "" : filenameMap.get(fileIndex - 1);
  }

  /**
   * Returns a new file index for a file.
   *
   * @param path  file path
   * @return  file index
   */
  public int createFileIndex(String path) {
    filenameMap.add(path);
    return filenameMap.size();
  }

  /**
   * Adds a new Simple object to the list compiled objects.
   *
   * @param objectSymbol  object being compiled
   */
  public void addObject(ObjectSymbol objectSymbol) {
    objects.add(objectSymbol);
  }

  /**
   * Adds the given class to the list of generated classes.
   *
   * @param internalName  generated class
   */
  public void addClassfile(String internalName) {
    classfiles.add(internalName + ".class");
  }

  /**
   * Returns the internal name of the implementation for the given component
   * object type.
   *
   * @param type  component object type
   * @return  internal name of component object
   */
  public String getComponentImplementationInternalName(ObjectType type) {
    return runtimeLoader.getComponentImplementationInternalName(type);
  }

  /**
   * 报告编译错误消息。
   *
   * @param position  源代码位置
   * @param error  error message template (see {@link Error})
   * @param params  parameters for error message template
   */
  public void error(long position, String error, String... params) {
    errorCount++;
    new Error(position, error, params).print(this, err);
  }

  /**
   * 报告编译警告消息。
   *
   * @param warning  警告信息
   */
  public void warning(Warning warning) {
    warningCount++;
    warning.print(this, out);
  }

  /**
   * 返回编译错误数。
   *
   * @return  编译错误计数
   */
  public int getErrorCount() {
    return errorCount;
  }

  /**
   * 返回编译警告数。
   *
   * @return  编译警告计数
   */
  public int getWarningCount() {
    return warningCount;
  }

  /**
   * 返回全局（未命名）命名空间的符号。
   *
   * @return  全局命名空间符号
   */
  public NamespaceSymbol getGlobalNamespaceSymbol() {
    return globalNamespaceSymbol;
  }

  /**
   * 返回运行时错误的基本对象类型。
   *
   * @return  运行时错误类型
   */
  public ObjectType getRuntimeErrorType() {
    return runtimeErrorType;
  }

  /**
   * 返回当前编译目标平台。
   *
   * @return  目标平台
   */
  public Platform getPlatform() {
    return platform;
  }

  /**
   * 触发编译的符号解析阶段。
   *
   * 注意：可视（public）仅用于测试！除非出于测试目的，否则不要从此类外部调用。
   */
  public void resolve() {
    // 首先解决声明
    for (ObjectSymbol objectSymbol: objects) {
      objectSymbol.resolve(this, null);
    }

    // 既然已经解析了所有声明，我们也尝试解析函数体。
    // 分两步进行这样做的好处是，我们不必担心循环问题。
    for (ObjectSymbol objectSymbol: objects) {
      objectSymbol.resolveFunctionBodies(this);
    }
  }

  /*
   * 触发类文件生成。
   */
  private void generate(File buildDirectory) {
    for (ObjectSymbol objectSymbol: objects) {
      objectSymbol.generate(this, buildDirectory);
    }
  }

  /*
   * 内存类文件生成中的触发器。用于单元测试。
   */
  private void generate(List<ClassFile> classes) {
    for (ObjectSymbol objectSymbol: objects) {
      objectSymbol.generate(this, classes);
    }
  }

  /**
   * 添加Simple项目需要服务。
   * 
   * @param internalName 内部类名
   */
  public void addSimpleNeedingService(String internalName) {
    // 将服务单元内部类名（simple/samples/test/MyService）转为服务类名（simple.samples.test.MyService）
    simpleNeedingServices.add(internalName.replace("/", "."));
    System.out.println("添加需要服务；对象：" + internalName);
    
  }
}
